/**
 * @fileoverview The helper class for VML graphics.
 * @link http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
 * @link https://developers.google.com/closure/compiler/docs/js-for-compiler
 */



/**
 * The helper class for VML graphics.
 * Based on raphael.js library.
 * @link http://www.w3.org/TR/NOTE-VML#_Toc416858391
 * @link http://www.w3.org/TR/SVG/paths.html#PathData
 * @link https://raw.github.com/DmitryBaranovskiy/raphael/master/raphael.js
 * @constructor
 */
graphics.VmlHelper = function() {

  /**
   * Converts SVG path string to VML path string.
   * @link https://raw.github.com/DmitryBaranovskiy/raphael/master/raphael.js
   * @param {string} svgPath The SVG path string.
   * @return {string} Returns converted VML path string.
   */
  this.getVmlPath = function(svgPath) {
    /** @type {!Object.<string, string>} */
    var map = {'M': 'm', 'L': 'l', 'C': 'c', 'Z': 'x',
      'm': 't', 'l': 'r', 'c': 'v', 'z': 'x'};

    /** @type {number} */ var zoom = 1; // 21600
    /** @type {Function} */
    var command = (/[ahqstv]/ig).test(svgPath) ? pathToCurve_ : pathToAbsolute_;

    if (command == pathToAbsolute_ && !(/[clmz]/g).test(svgPath)) {
      return svgPath.replace(/([clmz]),?([^clmz]*)/gi, function(mt, cmd, args) {
        /** @type {!Array.<number>} */ var values = [];
        /** @type {boolean} */ var isMove = cmd.toLowerCase() == 'm';
        /** @type {string} */ var str = map[cmd];
        args.replace(/-?[^,\s-]+/g, function(value) {
          if (isMove && values.length == 2) {
            str += values + map[cmd == 'm' ? 'l' : 'L'];
            values = [];
          }
          values.push(Math.round(value * zoom));
        });
        return str + values;
      });
    }

    /** @type {!Array} */ var pathArray = command(svgPath);
    /** @type {!Array} */ var result = [];
    for (/** @type {number} */ var i = 0; i < pathArray.length; i++) {
      /** @type {Array} */ var path = pathArray[i];
      /** @type {string} */ var cmd = path[0].toLowerCase();
      /** @type {number} */ var length = path.length;
      cmd = cmd == 'z' ? map[cmd] : cmd;
      for (/** @type {number} */ var j = 1; j < length; j++) {
        cmd += Math.round(path[j] * zoom) + (j != length - 1 ? ',' : '');
      }
      result.push(cmd);
    }
    return result.join(' ');
  };


  /**
   * @param {Function} f Function.
   * @param {*=} opt_scope Optional scope.
   * @param {Function=} opt_postprocessor Optional postprocessor function.
   * @return {Function} Returns function.
   * @private
   */
  function cacher_(f, opt_scope, opt_postprocessor) {
    function newf() {
      var arg = Array.prototype.slice.call(arguments, 0),
          args = arg.join('\u2400'),
          cache = newf.cache = newf.cache || {},
          count = newf.count = newf.count || [];
      //if (cache['hasOwnProperty'](args)) {
      //  repush(count, args);
      //  return opt_postprocessor ? opt_postprocessor(cache[args]) :
      //                             cache[args];
      //}
      //count.length >= 1e3 && delete cache[count.shift()];
      //count.push(args);
      cache[args] = f.apply(opt_scope, arg);
      return opt_postprocessor ? opt_postprocessor(cache[args]) : cache[args];
    }
    return newf;
  }


  function lineToCurve_(x1, y1, x2, y2) {
    return [x1, y1, x2, y2, x2, y2];
  }


  function quadraticToCurve_(x1, y1, ax, ay, x2, y2) {
    var _13 = 1 / 3,
        _23 = 2 / 3;
    return [
      _13 * x1 + _23 * ax,
      _13 * y1 + _23 * ay,
      _13 * x2 + _23 * ax,
      _13 * y2 + _23 * ay,
      x2,
      y2
    ];
  };

  /**
   * @type {Function}
   * @private
   */
  var pathToCurve_ = cacher_(function(path, path2) {
    var pth = !path2 && paths_(path);
    if (!path2 && pth.curve) {
      return pathClone_(pth.curve);
    }
    var p = pathToAbsolute_(path),
        p2 = path2 && pathToAbsolute_(path2),
        attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
        attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null};

    function processPath(path, d) {
      var nx, ny;
      if (!path) {
        return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
      }
      !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
      switch (path[0]) {
        case 'M':
          d.X = path[1];
          d.Y = path[2];
          break;
        case 'A':
          path = ['C'].concat(
              arcToCurve_.apply(0, [d.x, d.y].concat(path.slice(1))));
          break;
        case 'S':
          nx = d.x + (d.x - (d.bx || d.x));
          ny = d.y + (d.y - (d.by || d.y));
          path = ['C', nx, ny].concat(path.slice(1));
          break;
        case 'T':
          d.qx = d.x + (d.x - (d.qx || d.x));
          d.qy = d.y + (d.y - (d.qy || d.y));
          path = ['C'].concat(
              quadraticToCurve_(d.x, d.y, d.qx, d.qy, path[1], path[2]));
          break;
        case 'Q':
          d.qx = path[1];
          d.qy = path[2];
          path = ['C'].concat(
              quadraticToCurve_(d.x, d.y, path[1], path[2], path[3], path[4]));
          break;
        case 'L':
          path = ['C'].concat(lineToCurve_(d.x, d.y, path[1], path[2]));
          break;
        case 'H':
          path = ['C'].concat(lineToCurve_(d.x, d.y, path[1], d.y));
          break;
        case 'V':
          path = ['C'].concat(lineToCurve_(d.x, d.y, d.x, path[1]));
          break;
        case 'Z':
          path = ['C'].concat(lineToCurve_(d.x, d.y, d.X, d.Y));
          break;
      }
      return path;
    }

    function fixArc(pp, i) {
      if (pp[i].length > 7) {
        pp[i].shift();
        var pi = pp[i];
        while (pi.length) {
          pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
        }
        pp.splice(i, 1);
        ii = Math.max(p.length, p2 && p2.length || 0);
      }
    }

    function fixM(path1, path2, a1, a2, i) {
      if (path1 && path2 && path1[i][0] == 'M' && path2[i][0] != 'M') {
        path2.splice(i, 0, ['M', a2.x, a2.y]);
        a1.bx = 0;
        a1.by = 0;
        a1.x = path1[i][1];
        a1.y = path1[i][2];
        ii = Math.max(p.length, p2 && p2.length || 0);
      }
    }

    for (var i = 0, ii = Math.max(p.length, p2 && p2.length || 0);
        i < ii; i++) {
      p[i] = processPath(p[i], attrs);
      fixArc(p, i);
      p2 && (p2[i] = processPath(p2[i], attrs2));
      p2 && fixArc(p2, i);
      fixM(p, p2, attrs, attrs2, i);
      fixM(p2, p, attrs2, attrs, i);
      var seg = p[i],
              seg2 = p2 && p2[i];
      /** @type {number} */ var seglen = seg.length;
      /** @type {number} */ var seg2len = p2 ? seg2.length : 0;
      attrs.x = seg[seglen - 2];
      attrs.y = seg[seglen - 1];
      attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
      attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
      attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x);
      attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y);
      attrs2.x = p2 && seg2[seg2len - 2];
      attrs2.y = p2 && seg2[seg2len - 1];
    }
    if (!p2) {
      pth.curve = pathClone_(p);
    }
    return p2 ? [p, p2] : p;
  }, null, pathClone_);

  /**
   * @param {*} pathArray The SVG path string.
   * @return {*} Returns converted path to curve.
   * @private
   */
  function pathToAbsolute_(pathArray) {
    var pth = paths_(pathArray);
    if (pth.abs) {
      return pathClone_(pth.abs);
    }
    if (!isArray_(pathArray) || !isArray_(pathArray && pathArray[0])) {
      pathArray = parsePathString_(pathArray);
    }
    if (!pathArray || !pathArray.length) {
      return [['M', 0, 0]];
    }

    var res = [],
        x = 0,
        y = 0,
        mx = 0,
        my = 0,
        start = 0;
    if (pathArray[0][0] == 'M') {
      x = +pathArray[0][1];
      y = +pathArray[0][2];
      mx = x;
      my = y;
      start++;
      res[0] = ['M', x, y];
    }
    var crz = pathArray.length == 3 && pathArray[0][0] == 'M' &&
              pathArray[1][0].toUpperCase() == 'R' &&
              pathArray[2][0].toUpperCase() == 'Z';

    for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
      res.push(r = []);
      pa = pathArray[i];
      if (pa[0] != String.prototype.toUpperCase.call(pa[0])) {
        r[0] = String.prototype.toUpperCase.call(pa[0]);
        switch (r[0]) {
          case 'A':
            r[1] = pa[1];
            r[2] = pa[2];
            r[3] = pa[3];
            r[4] = pa[4];
            r[5] = pa[5];
            r[6] = +(pa[6] + x);
            r[7] = +(pa[7] + y);
            break;
          case 'V':
            r[1] = +pa[1] + y;
            break;
          case 'H':
            r[1] = +pa[1] + x;
            break;
          case 'R':
            var dots = [x, y].concat(pa.slice(1));
            for (var j = 2, jj = dots.length; j < jj; j++) {
              dots[j] = +dots[j] + x;
              dots[++j] = +dots[j] + y;
            }
            res.pop();
            res = res.concat(catmullRom2bezier(dots, crz));
            break;
          case 'M':
            mx = +pa[1] + x;
            my = +pa[2] + y;
          default:
            for (j = 1, jj = pa.length; j < jj; j++) {
              r[j] = +pa[j] + ((j % 2) ? x : y);
            }
        }
      } else if (pa[0] == 'R') {
        dots = [x, y].concat(pa.slice(1));
        res.pop();
        res = res.concat(catmullRom2bezier(dots, crz));
        r = ['R'].concat(pa.slice(-2));
      } else {
        for (var k = 0, kk = pa.length; k < kk; k++) {
          r[k] = pa[k];
        }
      }

      switch (r[0]) {
        case 'Z':
          x = mx;
          y = my;
          break;
        case 'H':
          x = r[1];
          break;
        case 'V':
          y = r[1];
          break;
        case 'M':
          mx = r[r.length - 2];
          my = r[r.length - 1];
        default:
          x = r[r.length - 2];
          y = r[r.length - 1];
      }
    }
    res.toString = pathToString_;
    pth.abs = pathClone_(res);
    return res;
  }

  function paths_(ps) {
    /** @type {!Object} */ var p = paths_.ps = paths_.ps || {};
    if (p[ps]) {
      p[ps].sleep = 100;
    } else {
      p[ps] = {sleep: 100};
    }

    setTimeout(function() {
      for (/** @type {string} */ var key in p) {
        if (p.hasOwnProperty(key) && key != ps) {
          p[key].sleep--;
          !p[key].sleep && delete p[key];
        }
      }
    }, 0);
    return p[ps];
  }

  /**
   * @param {*} pathArray The path array.
   * @return {Array} Returns cloned path array.
   * @private
   */
  function pathClone_(pathArray) {
    /** @type {Object} */ var result = clone_(pathArray);
    result.toString = pathToString_;
    return /** @type {Array} */ (result);
  }

  /**
   * @param {*} obj The object to clone.
   * @return {Object} Return cloned object.
   * @private
   */
  function clone_(obj) {
    if (Object(obj) !== obj) {
      return /** @type {Object} */ (obj);
    }

    /** @type {Object} */ var result = new obj.constructor;
    for (/** @type {string} */ var key in obj) {
      if (obj.hasOwnProperty(key)) {
        result[key] = clone_(obj[key]);
      }
    }
    return result;
  }

  /**
   * @return {string} Returns path array as string.
   * @this {Array}
   * @private
   */
  function pathToString_() {
    // "this" is Array
    return this.join(',').replace(/,?([achlmqrstvxz]),?/gi, '$1');
  }

  /**
   * @param {*} obj The object to check.
   * @return {boolean} Returns true if passed obj is Array, false otherwise.
   * @private
   */
  function isArray_(obj) {
    return obj ? (obj instanceof Array) : false;
  }

  // replaced "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002" +
  //          "\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f" +
  //          "\u3000\u2028\u2029"
  // with "\s"
  var pathCommand =
      /([achlmrqstvz])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig;
  var pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\s]*,?[\s]*/ig;

  function parsePathString_(pathString) {
    if (!pathString) {
      return null;
    }
    var pth = paths_(pathString);
    if (pth.arr) {
      return pathClone_(pth.arr);
    }

    var paramCounts = {'a': 7, 'c': 6, 'h': 1, 'l': 2, 'm': 2,
      'r': 4, 'q': 4, 's': 4, 't': 2, 'v': 1, 'z': 0},
        data = [];
    if (isArray_(pathString) && isArray_(pathString[0])) {
      data = pathClone_(pathString);
    }
    if (!data.length) {
      pathString.replace(pathCommand, function(a, b, c) {
        var params = [],
            name = b.toLowerCase();
        c.replace(pathValues, function(a, b) {
          b && params.push(+b);
        });
        if (name == 'm' && params.length > 2) {
          data.push([b].concat(params.splice(0, 2)));
          name = 'l';
          b = b == 'm' ? 'l' : 'L';
        }
        if (name == 'r') {
          data.push([b].concat(params));
        } else while (params.length >= paramCounts[name]) {
          data.push([b].concat(params.splice(0, paramCounts[name])));
          if (!paramCounts[name]) {
            break;
          }
        }
      });
    }
    data.toString = pathToString_;
    pth.arr = pathClone_(data);
    return data;
  }

  function catmullRom2bezier(crp, z) {
    var d = [];
    for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
      var p = [
        {x: +crp[i - 2], y: +crp[i - 1]},
        {x: +crp[i], y: +crp[i + 1]},
        {x: +crp[i + 2], y: +crp[i + 3]},
        {x: +crp[i + 4], y: +crp[i + 5]}
      ];
      if (z) {
        if (!i) {
          p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
        } else if (iLen - 4 == i) {
          p[3] = {x: +crp[0], y: +crp[1]};
        } else if (iLen - 2 == i) {
          p[2] = {x: +crp[0], y: +crp[1]};
          p[3] = {x: +crp[2], y: +crp[3]};
        }
      } else {
        if (iLen - 4 == i) {
          p[3] = p[2];
        } else if (!i) {
          p[0] = {x: +crp[i], y: +crp[i + 1]};
        }
      }
      d.push(['C',
        (-p[0].x + 6 * p[1].x + p[2].x) / 6,
        (-p[0].y + 6 * p[1].y + p[2].y) / 6,
        (p[1].x + 6 * p[2].x - p[3].x) / 6,
        (p[1].y + 6 * p[2].y - p[3].y) / 6,
        p[2].x,
        p[2].y
      ]);
    }

    return d;
  }

  function arcToCurve_(
      x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
    // for more information of where this math came from visit:
    // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
    var _120 = Math.PI * 120 / 180,
        rad = Math.PI / 180 * (+angle || 0),
        res = [],
        xy,
        rotate = cacher_(function(x, y, rad) {
          var X = x * Math.cos(rad) - y * Math.sin(rad),
                Y = x * Math.sin(rad) + y * Math.cos(rad);
          return {x: X, y: Y};
        });
    if (!recursive) {
      xy = rotate(x1, y1, -rad);
      x1 = xy.x;
      y1 = xy.y;
      xy = rotate(x2, y2, -rad);
      x2 = xy.x;
      y2 = xy.y;
      var cos = Math.cos(Math.PI / 180 * angle),
          sin = Math.sin(Math.PI / 180 * angle),
          x = (x1 - x2) / 2,
          y = (y1 - y2) / 2;
      var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
      if (h > 1) {
        h = Math.sqrt(h);
        rx = h * rx;
        ry = h * ry;
      }
      var rx2 = rx * rx,
          ry2 = ry * ry,
          k = (large_arc_flag == sweep_flag ? -1 : 1) *
                Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (
                  rx2 * y * y + ry2 * x * x))),
          cx = k * rx * y / ry + (x1 + x2) / 2,
          cy = k * -ry * x / rx + (y1 + y2) / 2,
          f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
          f2 = Math.asin(((y2 - cy) / ry).toFixed(9));

      f1 = x1 < cx ? Math.PI - f1 : f1;
      f2 = x2 < cx ? Math.PI - f2 : f2;
      f1 < 0 && (f1 = Math.PI * 2 + f1);
      f2 < 0 && (f2 = Math.PI * 2 + f2);
      if (sweep_flag && f1 > f2) {
        f1 = f1 - Math.PI * 2;
      }
      if (!sweep_flag && f2 > f1) {
        f2 = f2 - Math.PI * 2;
      }
    } else {
      f1 = recursive[0];
      f2 = recursive[1];
      cx = recursive[2];
      cy = recursive[3];
    }
    var df = f2 - f1;
    if (Math.abs(df) > _120) {
      var f2old = f2,
          x2old = x2,
          y2old = y2;
      f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
      x2 = cx + rx * Math.cos(f2);
      y2 = cy + ry * Math.sin(f2);
      res = arcToCurve_(x2, y2, rx, ry, angle, 0,
          sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
    }
    df = f2 - f1;
    var c1 = Math.cos(f1),
        s1 = Math.sin(f1),
        c2 = Math.cos(f2),
        s2 = Math.sin(f2),
        t = Math.tan(df / 4),
        hx = 4 / 3 * rx * t,
        hy = 4 / 3 * ry * t,
        m1 = [x1, y1],
        m2 = [x1 + hx * s1, y1 - hy * c1],
        m3 = [x2 + hx * s2, y2 - hy * c2],
        m4 = [x2, y2];
    m2[0] = 2 * m1[0] - m2[0];
    m2[1] = 2 * m1[1] - m2[1];
    if (recursive) {
      return [m2, m3, m4].concat(res);
    } else {
      res = [m2, m3, m4].concat(res).join().split(',');
      var newres = [];
      for (var i = 0, ii = res.length; i < ii; i++) {
        newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y :
                            rotate(res[i], res[i + 1], rad).x;
      }
      return newres;
    }
  }
};


/**
 * Converts SVG path string to VML path string.
 * Based on raphael.js library.
 * @link https://raw.github.com/DmitryBaranovskiy/raphael/master/raphael.js
 * @param {string} svgPath The SVG path string.
 * @return {string} Returns converted VML path string.
 * @static
 */
graphics.VmlHelper.getVmlPath = function(svgPath) {
  if (!graphics.VmlHelper.helper_) {
    graphics.VmlHelper.helper_ = new graphics.VmlHelper;
  }
  return graphics.VmlHelper.helper_.getVmlPath(svgPath);
};

